package com.gnizr.gnivz;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

import net.sf.json.JSON;
import net.sf.json.JSONSerializer;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.log4j.Logger;

import com.gnizr.db.dao.User;
import com.gnizr.web.action.AbstractAction;
import com.gnizr.web.action.LoggedInUserAware;

public class GetTagsAction extends AbstractAction implements LoggedInUserAware {
	
	public static final long serialVersionUID = 01L;

    private static final Logger logger = Logger.getLogger(GetTagsAction.class.getName());

	private Map<String,Integer> relatedTags;
	private User loggedInUser;

    private String tag;
    private boolean userDefinedRelationsEnabled;
    private boolean commonBookmarksEnabled;
    private boolean commonUsersEnabled;

    private JSON jsonResult;
	private BasicDataSource dataSource;
	
	private int userDefinedRelationsWeight = 50;
	private int commonBookmarksWeight = 25;
	private int commonUsersWeight = 5;

	public GetTagsAction(){
		super();
		userDefinedRelationsEnabled=true;
		commonBookmarksEnabled=true;
		commonUsersEnabled=true;
	}
		
	public void setLoggedInUser(User user) {
		this.loggedInUser = user;		
	}

    public String getTag() {
        return tag;
      }

    public void setTag(String tag) {
        this.tag = tag;
      }
	
    public boolean getCommonBookmarksEnabled() {
        return commonBookmarksEnabled;
      }

    public void setCommonBookmarksEnabled(boolean commonBookmarksEnabled) {
        this.commonBookmarksEnabled = commonBookmarksEnabled;
      }
	
    public boolean getCommonUsersEnabled() {
        return commonUsersEnabled;
      }

    public void setCommonUsersEnabled(boolean commonUsersEnabled) {
        this.commonUsersEnabled = commonUsersEnabled;
      }
	
    public boolean getUserDefinedRelationsEnabled() {
        return userDefinedRelationsEnabled;
      }

    public void setUserDefinedRelationsEnabled(boolean userDefinedRelationsEnabled) {
        this.userDefinedRelationsEnabled = userDefinedRelationsEnabled;
      }
	
	protected void setupDataSource() {
		dataSource = new BasicDataSource();
		dataSource.setUsername("gnizr");
		dataSource.setPassword("gnizrpass");
		dataSource.setUrl("jdbc:mysql://localhost/gnizr_db");
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.addConnectionProperty("characterEncoding", "UTF-8");
		dataSource.addConnectionProperty("useUnicode", "TRUE");		
	}
	
    @Override
	protected String go() throws Exception {
		logger.debug("GetTagsAction go() called");
		logger.debug("UserDefineRelationsEnabled = "+userDefinedRelationsEnabled);
		logger.debug("CommonBookmarksEnabled = "+commonBookmarksEnabled);
		logger.debug("CommonUsersEnabled = "+commonUsersEnabled);

		if (dataSource == null) 
			setupDataSource();

//		GnizrDao gnizrDao = GnizrDao.getInstance(dataSource);

		try {
			relatedTags = getRelatedTags(tag);
			System.out.println("GetTagsAction: given tag "+tag+" we found = "+
					relatedTags.size()+" related tags");
			
		} catch (Exception e) {
			logger.error("error: " + e.getMessage());
		}

		return SUCCESS;
	}
	
	public JSON getJsonResult() {
		jsonResult = null;
		if (tag == null)
			return null;
		try {
//			Map<String,List<Map<String,String>>> graphMap = 
//				new HashMap<String, List<Map<String,String>>>();
			Map<String,Object> graphMap = 
				new HashMap<String, Object>();
			List<Map<String,String>> nodeArray =  new ArrayList<Map<String,String>>();

			for (String rt : relatedTags.keySet()) {
				Map<String,String> nodeMap = new HashMap<String, String>();
				nodeMap.put("name", rt);
				nodeMap.put("url", "http://localhost:8080/gnizr/tag/"+rt);
				nodeArray.add(nodeMap);
			}
			graphMap.put("node_array", nodeArray);
			
			List<Map<String,String>> edgeArray =  new ArrayList<Map<String,String>>();
			for (String rt : relatedTags.keySet()) {
				Map<String,String> edgeMap = new HashMap<String, String>();
				edgeMap.put("from", tag);
				edgeMap.put("to", rt);
				edgeMap.put("weight", relatedTags.get(rt).toString());
				edgeArray.add(edgeMap);
			}
			graphMap.put("edge_array", edgeArray);
			
			Map<String,String> hoverDict = new HashMap<String, String>();
			for (String rt : relatedTags.keySet()) {
				String edgeKey,hoverValue;
				
				edgeKey = String.format("%s_%s",tag,rt);
				hoverValue = String.format("%s is related to %s with weight %d",
						tag, rt, relatedTags.get(rt));
				
				hoverDict.put(edgeKey.toString(), hoverValue.toString());
			}
			graphMap.put("hover_dict", hoverDict);

			jsonResult = JSONSerializer.toJSON(graphMap);
		
		} catch(Exception e) {
			final String err = "can't transform objects to JSON";
			logger.error(err,e);
		}
		
		return jsonResult;
	}
	
	private Map<String,Integer> getRelatedTags(String tag) {
		Map<String,Integer> map = new HashMap<String, Integer>();
		map.put(tag, 0); // Add the selected tag as the root (i.e. weight = 0)
		try {
			if (this.getUserDefinedRelationsEnabled()) {
				map.putAll(this.getUserDefinedRelatedTags(tag));
			}
			if (this.getCommonBookmarksEnabled()) {
//				map.putAll(this.getCommonBookmarkRelatedTags(tag));
				addRelatedTagsToMap(map,this.getCommonBookmarkRelatedTags(tag));

			}
			if (this.getCommonUsersEnabled()) {
//				map.putAll(this.getCommonUserRelatedTags(tag));
				addRelatedTagsToMap(map,this.getCommonUserRelatedTags(tag));
			}
		} catch (Exception e) {
			final String err = "can't getRelatedTags";
			logger.error(err,e);
		}
		
		return map;
	}
	
	private Map<String,Integer> getUserDefinedRelatedTags(String tag) throws SQLException {
		Map<String,Integer> map = new HashMap<String, Integer>();
		
		// Get tags that are the object of the relationship with the given subject tag
		Connection c = dataSource.getConnection();
     	PreparedStatement ps = c.prepareStatement(
     			"select tag,count(*) from tag as Tag, tag_assertion as TA " +
     			"where (select TA.object_id - 1) = Tag.id and " +
     			"TA.subject_id = (select (select id from tag where tag = ?) + 1) " +
     			"group by tag");
     	ps.setString(1, tag);
     	ResultSet rs = ps.executeQuery();
     	while (rs.next()) {
     		String relatedTag = rs.getString("tag");
     		int tagCount = rs.getInt(2);
     		if (map.containsKey(relatedTag))
     			tagCount += map.get(relatedTag);
     		logger.debug("getUserDefinedRelatedTags adding " + relatedTag + " with count=" + tagCount + " to map");
     		map.put(relatedTag, tagCount * userDefinedRelationsWeight);
     	}
     	
		// Get tags that are the subject of the relationship with the given object tag
     	ps = c.prepareStatement(
     			"select tag,count(*) from tag as Tag, tag_assertion as TA " +
     			"where (select TA.subject_id + 1) = Tag.id and " +
     			"TA.object_id = (select (select id from tag where tag = ?) - 1) " +
     			"group by tag");
     	ps.setString(1, tag);
     	rs = ps.executeQuery();
     	while (rs.next()) {
     		String relatedTag = rs.getString("tag");
     		int tagCount = rs.getInt(2);
     		if (map.containsKey(relatedTag))
     			tagCount += map.get(relatedTag);
     		logger.debug("getUserDefinedRelatedTags adding " + relatedTag + " with count=" + tagCount + " to map");
     		map.put(relatedTag, new Integer(tagCount) * userDefinedRelationsWeight);
     	}
     	
     	return map;
	}
	
	private Map<String, Integer> getCommonBookmarkRelatedTags(String tag) throws SQLException {
		Map<String,Integer> map = new HashMap<String, Integer>();
		
		// Get links for the given subject tag
		Connection c = dataSource.getConnection();
     	PreparedStatement ps = c.prepareStatement(
     			"select link_id from link_tag_idx as LT, link as Link " +
     			"where LT.link_id = Link.id " +
     			"and LT.tag_id = (select id from tag where tag = ?) ");
     	ps.setString(1, tag);
     	ResultSet rs = ps.executeQuery();
     	while (rs.next()) {
     		int link_id = rs.getInt(1);
     		
     		// Get tags for a link
     		PreparedStatement ips = c.prepareStatement(
     				"select tag from tag as Tag, link_tag_idx as LT " +
     				"where LT.tag_id = Tag.id and LT.link_id = ?");
     		ips.setInt(1, link_id);
     		ResultSet irs = ips.executeQuery();
     		while (irs.next()) {
         		String relatedTag = irs.getString("tag");
     			// Ignore the subject tag
         		if (relatedTag.compareTo(tag) == 0)
         			continue;
         		
         		int tagCount = 0;
         		if (map.containsKey(relatedTag))
         			tagCount = map.get(relatedTag);
         		tagCount += 1;
         		logger.debug("getCommonBookmarkRelatedTags adding " + relatedTag + " with count=" + tagCount + " to map");
         		map.put(relatedTag, new Integer(tagCount) * commonBookmarksWeight);
     		}
     	}

     	return map;
	}

	private Map<String, Integer> getCommonUserRelatedTags(String tag) throws SQLException {
		Map<String,Integer> map = new HashMap<String, Integer>();

		// Get users for the given subject tag
		Connection c = dataSource.getConnection();
     	PreparedStatement ps = c.prepareStatement(
     			"select user_id from user_tag_idx as UT, user as User " +
     			"where UT.user_id = User.id " +
     			"and UT.tag_id = (select id from tag where tag = ?) ");
     	ps.setString(1, tag);
     	ResultSet rs = ps.executeQuery();
     	while (rs.next()) {
     		int user_id = rs.getInt(1);
     		
     		// Get tags for a link
     		PreparedStatement ips = c.prepareStatement(
     				"select tag from tag as Tag, user_tag_idx as UT " +
     				"where UT.tag_id = Tag.id and UT.user_id = ?");
     		ips.setInt(1, user_id);
     		ResultSet irs = ips.executeQuery();
     		while (irs.next()) {
         		String relatedTag = irs.getString("tag");
     			// Ignore the subject tag
         		if (relatedTag.compareTo(tag) == 0)
         			continue;
         		
         		int tagCount = 0;
         		if (map.containsKey(relatedTag))
         			tagCount = map.get(relatedTag);
         		tagCount += 1;
         		logger.debug("getCommonUserRelatedTags adding " + relatedTag + " with count=" + tagCount + " to map");
         		map.put(relatedTag, new Integer(tagCount) * commonUsersWeight);
     		}
     	}

     	return map;
	}
	
	// This method inserts the related tags into an existing related tags map.
	// The maps store a weighted count for the given related tag name.
	// If a related tag key to add already exists, the weighted counts need to be 
	// combined additively.
	Map<String,Integer> addRelatedTagsToMap(Map<String,Integer> map, 
				Map<String,Integer> relatedTags) {
		for (String key : relatedTags.keySet()) {
			if (map.containsKey(key)) 
				map.put(key, map.get(key) + relatedTags.get(key));
			else
				map.put(key, relatedTags.get(key));
		}
		return map;
	}
}
